package relay

import (
	"context"
	"fmt"

	"gitee.com/mkwchecking/sr-core/config"
	"gitee.com/mkwchecking/sr-core/log"
	"gitee.com/mkwchecking/sr-core/registry"
	"gitee.com/mkwchecking/sr-core/storage/cache"
	"gitee.com/mkwchecking/sr-core/storage/database"
)

var instance = NewConstructor(context.Background(), config.Get())

func GetConstructor() *Constructor {
	return instance
}

func NewConstructor(ctx context.Context, cfg *config.Config) *Constructor {
	return &Constructor{
		ctx:     ctx,
		cfg:     cfg,
		depends: make(map[any]any),
	}
}

type Constructor struct {
	ctx context.Context
	cfg *config.Config

	depends map[any]any
}

func (c *Constructor) Get(key any) (any, bool) {
	v := c.ctx.Value(key)
	if v == nil {
		return nil, false
	}

	return v, true
}

func (c *Constructor) SetContext(ctx context.Context) {
	if len(c.depends) > 0 {
		for k, v := range c.depends {
			ctx = context.WithValue(ctx, k, v)
		}
	}

	c.ctx = ctx
}

func (c *Constructor) SetConfig(cfg *config.Config) {
	c.cfg = cfg
}

func (c *Constructor) Install(ctx context.Context, cfg *config.Config) (context.Context, error) {
	c.SetConfig(cfg)
	c.SetContext(ctx)
	return c.install(cfg)
}

func (c *Constructor) install(cfg *config.Config) (context.Context, error) {
	if err := c.setupLogger(cfg); err != nil {
		return c.ctx, err
	}

	if err := c.setupRegistry(cfg); err != nil {
		return c.ctx, err
	}

	if err := c.setupCache(cfg); err != nil {
		return c.ctx, err
	}

	if err := c.setupDataBase(cfg); err != nil {
		return c.ctx, err
	}

	return c.ctx, nil
}

func (c *Constructor) setupCache(cfg *config.Config) error {
	if cfg.Cache == nil {
		return nil
	}

	cacheSet, err := cache.Install(cfg.Cache)
	if err != nil {
		return err
	}

	cacheItem, ok := cacheSet.GetDefault()
	if !ok {
		return fmt.Errorf("default cache not found : %s", cfg.Cache.Addr)
	}

	c.ctx = MixToContext(func(ctx context.Context) context.Context {
		return cache.WithCtx(c.ctx, cacheItem)
	})
	c.depends[cache.YCacheKey{}] = cacheItem

	return nil
}

func (c *Constructor) setupLogger(cfg *config.Config) error {
	if cfg.Log == nil {
		// use default internal log
		logCfg := log.StdoutConfig()
		cfg.Log = &logCfg
	}
	logger, err := log.Install(*cfg.Log)
	if err != nil {
		return err
	}

	c.ctx = MixToContext(func(ctx context.Context) context.Context {
		return log.WithCtx(c.ctx, logger)
	})
	c.depends[log.YLogKey{}] = logger
	return nil
}

func (c *Constructor) setupDataBase(cfg *config.Config) error {
	if cfg.Database == nil {
		return nil
	}

	logger, ok := log.FromCtx(c.ctx)
	if !ok {
		return fmt.Errorf("default logger not found")
	}

	dataset, err := database.Install(cfg.Database, logger)
	if err != nil {
		return err
	}

	c.ctx = MixToContext(func(ctx context.Context) context.Context {
		return database.WithCtx(c.ctx, dataset)
	})

	c.depends[database.YDatabaseKey{}] = dataset

	return nil
}

func (c *Constructor) setupRegistry(cfg *config.Config) error {
	if cfg.Discovery == nil {
		return nil
	}

	regis, err := registry.Install(cfg.Discovery)
	if err != nil {
		return err
	}

	// c.ctx =
	c.ctx = MixToContext(func(ctx context.Context) context.Context {
		return registry.WithCtx(c.ctx, regis)
	})

	c.depends[registry.YRegistryKey{}] = regis
	return nil
}
